import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.ImageIcon;
import javax.swing.Timer;
import java.io.*;


import sun.audio.*;

import java.util.ArrayList;
import java.util.Random;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.Color;
import java.awt.Font;

public class Board extends JPanel implements ActionListener 
{
	private Timer timerBack, timerChar, timerGame, timerEnemy;
	private Character char1;
	private Image Background1;
	private Image paused;
	private int backY = 0;
	private int backDif = 2;
	private int charAccl = 0;
	private int scoreDif = 1;
	private ArrayList<Enemies> es = new ArrayList<Enemies>();
	private AudioStream as;
	private File gameM =new File("GameMusic.wav");
	private InputStream gameMusic;

		
	
	
	public Board()
	{
		try{
		this.gameMusic = new FileInputStream(gameM);
		this.as = new AudioStream(gameMusic);
		} catch (IOException e) {
            e.printStackTrace();
         } 
		
		//Creates images of the background and the paused logo
		ImageIcon ii = new ImageIcon("Background1.jpg", "background");
		Background1 = ii.getImage();
		
		ImageIcon iip = new ImageIcon("Paused.jpg", "paused");
		paused = iip.getImage();
	
		//Adds a listener to the timer of the board, named timerGame
		//Double buffers the image to stop them being drawn partially
		addKeyListener(new TimeAdapter());
		setFocusable(true);
		setDoubleBuffered(true);
		
		//Creates a new character image, multiple character may be added later
		char1 = new Character();
		
	
		//Moves the background every 4 milliseconds and increases the score
		timerBack = new Timer(4, new ActionListener(){
			public void actionPerformed(ActionEvent ae)
			{
				moveBackground();
				char1.increaseScore(scoreDif);
			}
		});
		
		//Move the character every 200 milliseconds 
		timerChar = new Timer(400, new ActionListener(){
			public void actionPerformed(ActionEvent ae)
			{
				moveCharacter();
				
				
			}
		});
	
		//Creates a new enemy every 600 milliseconds, either 1 new enemy 
		//a set of enemies in a present formation
		timerEnemy = new Timer(600, new ActionListener(){
			public void actionPerformed(ActionEvent ae)
			{
				
				Random generator = new Random();
				int r = generator.nextInt(10);
				if(r==7)
				{
					newFormation();
				}
				else
				{
					newEnemy();
				}
				
			}
		});
		
		//Creates all timers that will run independently except the main game timer
		//that will change how the Board object changes over timer. Every 2 milliseconds
		//it will check to see if anything has changed in the game
		timerGame = new Timer(1, this);
		timerEnemy.start();
		timerGame.start();
		timerChar.start();
		timerBack.start();
		startMusic();
		
		
	}
	
	//Sets the positions of a whole range of enemies to create present formations
	//These are all added to the array list at the same time
	public void newFormation()
	{
		//Creates a random number between 1 and 10 to choose which formation should be called
		Random generator = new Random();
		int r = generator.nextInt(10);
		
		//Enemies are disabled from spawning for a specific time to stop enemies spawning over each other
		
		//Slash from right
		if(r==1)
			
		{
			for(int i=0; i<11; i++)
			{
					es.add(new Enemies(i*25, 750+(i*75)));			
			}
			timerEnemy.stop();
			timerEnemy.setInitialDelay(2500);
			timerEnemy.start();
		}
		//Slash from left
		else if(r==2)
		{			
						
					es.add(new Enemies(400, 900));
					es.add(new Enemies(375, 975));
					es.add(new Enemies(350, 1050));
					es.add(new Enemies(325, 1125));
					es.add(new Enemies(300, 1200));
					es.add(new Enemies(275, 1275));
					es.add(new Enemies(250, 1350));
					es.add(new Enemies(225, 1425));
					es.add(new Enemies(200, 1500));
					es.add(new Enemies(175, 1575));
					es.add(new Enemies(150, 1650));
					timerEnemy.stop();
					timerEnemy.setInitialDelay(2500);
					timerEnemy.start();
	
		}
		// | || | formation (columns)
		else if(r==3)
		{
			es.add(new Enemies(0, 750));
			es.add(new Enemies(0, 825));
			es.add(new Enemies(0, 900));
			es.add(new Enemies(0, 975));
			es.add(new Enemies(0, 1050));
			es.add(new Enemies(0, 1125));
			es.add(new Enemies(0, 1200));
			
			es.add(new Enemies(400, 750));
			es.add(new Enemies(400, 825));
			es.add(new Enemies(400,900));
			es.add(new Enemies(400, 975));
			es.add(new Enemies(400, 1050));
			es.add(new Enemies(400, 1125));
			es.add(new Enemies(400, 1200));
			
			es.add(new Enemies(175, 750));
			es.add(new Enemies(175, 825));
			es.add(new Enemies(175, 900));
			es.add(new Enemies(175, 975));
			es.add(new Enemies(175, 1050));
			es.add(new Enemies(175, 1125));
			es.add(new Enemies(175, 1200));
			
			es.add(new Enemies(225, 750));
			es.add(new Enemies(225, 825));
			es.add(new Enemies(225, 900));
			es.add(new Enemies(225, 975));
			es.add(new Enemies(225, 1050));
			es.add(new Enemies(225, 1125));
			es.add(new Enemies(225, 1200));
			
			timerEnemy.stop();
			timerEnemy.setInitialDelay(1500);
			timerEnemy.start();
		
		}
		
		//Forward V
		else if(r==4)
		{
		
			es.add(new Enemies(200, 750));
			es.add(new Enemies(225, 825));
			es.add(new Enemies(175, 825));
			es.add(new Enemies(250, 900));
			es.add(new Enemies(150, 900));
			es.add(new Enemies(275, 975));
			es.add(new Enemies(125, 975));
			es.add(new Enemies(300, 1050));
			es.add(new Enemies(100, 1050));
			
			timerEnemy.stop();
			timerEnemy.setInitialDelay(2000);
			timerEnemy.start();
		}
		//Straight line from left
		else if(r==5)
		{
			es.add(new Enemies(0, 750));
			es.add(new Enemies(50, 750));
			es.add(new Enemies(100, 750));
			es.add(new Enemies(150, 750));
			es.add(new Enemies(200, 750));
			es.add(new Enemies(250, 750));
			
			timerEnemy.stop();
			timerEnemy.setInitialDelay(200);
			timerEnemy.start();
	

		}
		//Straight line from right
		else if(r==6)
		{
			es.add(new Enemies(400, 750));
			es.add(new Enemies(350, 750));
			es.add(new Enemies(300, 750));
			es.add(new Enemies(250, 750));
			es.add(new Enemies(200, 750));
			es.add(new Enemies(150, 750));
			
			timerEnemy.stop();
			timerEnemy.setInitialDelay(200);
			timerEnemy.start();
			
		}
		//Bottomless inverted V
		else if(r==7)
		{
			es.add(new Enemies(400, 900));
			es.add(new Enemies(375, 975));
			es.add(new Enemies(350, 1050));
			es.add(new Enemies(325, 1125));
			es.add(new Enemies(300, 1200));
			es.add(new Enemies(275, 1275));

			for(int i=0; i<6; i++)
			{
					es.add(new Enemies(i*25, 750+(i*75)));			
			}
	
			timerEnemy.stop();
			timerEnemy.setInitialDelay(1500);
			timerEnemy.start();
			
		}
		//Spaced pairs
		else if(r==8)
		{
			es.add(new Enemies(0, 750));
			es.add(new Enemies(50, 750));
			es.add(new Enemies(400, 750));
			es.add(new Enemies(350, 750));
			es.add(new Enemies(175, 750));
			es.add(new Enemies(225, 750));
			
			timerEnemy.stop();
			timerEnemy.setInitialDelay(1500);
			timerEnemy.start();
			
			
		}
		//Double S formations
		else if(r==9)
		{
			es.add(new Enemies(375, 900));
			es.add(new Enemies(350, 975));
			es.add(new Enemies(335, 1050));
			es.add(new Enemies(350, 1125));
			es.add(new Enemies(375, 1200));
			es.add(new Enemies(400, 1275));
			es.add(new Enemies(415, 1350));
			es.add(new Enemies(400, 1425));
			es.add(new Enemies(375, 1500));
			
			es.add(new Enemies(100, 900));
			es.add(new Enemies(75, 975));
			es.add(new Enemies(60, 1050));
			es.add(new Enemies(75, 1125));
			es.add(new Enemies(100, 1200));
			es.add(new Enemies(125, 1275));
			es.add(new Enemies(140, 1350));
			es.add(new Enemies(125, 1425));
			es.add(new Enemies(100, 1500));
			
			es.add(new Enemies(375, 1575));
			es.add(new Enemies(350, 1650));
			es.add(new Enemies(335, 1725));
			es.add(new Enemies(350, 1800));
			es.add(new Enemies(375, 1875));
			es.add(new Enemies(400, 1950));
			es.add(new Enemies(415, 2025));
			es.add(new Enemies(400, 2100));
			es.add(new Enemies(375, 2175));
			
			es.add(new Enemies(100, 1575));
			es.add(new Enemies(75, 1650));
			es.add(new Enemies(60, 1725));
			es.add(new Enemies(75, 1800));
			es.add(new Enemies(100, 1875));
			es.add(new Enemies(125, 1950));
			es.add(new Enemies(140, 2025));
			es.add(new Enemies(125, 2100));
			es.add(new Enemies(100, 2175));
			
			timerEnemy.stop();
			timerEnemy.setInitialDelay(4000);
			timerEnemy.start();
			
			
		}
		//Full line
		else 
		{
			es.add(new Enemies(0, 750));
			es.add(new Enemies(50, 750));
			es.add(new Enemies(100, 750));
			es.add(new Enemies(150, 750));
			es.add(new Enemies(200, 750));
			es.add(new Enemies(250, 750));
			es.add(new Enemies(400, 750));
			es.add(new Enemies(350, 750));
			es.add(new Enemies(300, 750));
			
			timerEnemy.stop();
			timerEnemy.setInitialDelay(300);
			timerEnemy.start();
			
			
		}
		
		
	}
	
	//Creates a new enemy at a random position along the x axis at the bottom of the screen
	public void newEnemy()
	{
		Random generator = new Random();
		int r = generator.nextInt(430);
		int rMoving = generator.nextInt(3);
		
		
		if((rMoving == 1))
		{
			es.add(new Enemies(r, 750, true));
		}
		else if((rMoving == 2))
		{
			es.add(new Enemies(r, 750, false));
		}
		else
		{
		es.add(new Enemies(r, 750));
		}
	}
	
	
	
	//Moves the background up the screen until it reaches a certain and then repeats
	public void moveBackground()
	{
		if(backY < -750)
		{
			backY = 0;
		}
		else
		{
		backY -= backDif;
		}
		
		return;
	}
	
	//Moves the character unless it is at the boundaries
	public void moveCharacter()
	{
		//Once character reaches the bottom they stop
		if((char1.getY()+charAccl) == 450)
		{
			char1.movey(0);
		}
		else
		{
			//Once character reaches certain points on the screen they will
			//will have enemies come towards them faster and receive and more
			//quickly increasing score
			if((char1.getY()+charAccl) > 200 && (char1.getY()+charAccl) < 300 )	
			{
				for (int i = 0; i < es.size(); i++ ) 
				{
					Enemies enemy = (Enemies) es.get(i);
					enemy.changeSpeed(1);
					this.scoreDif = 2;
					this.backDif = 3;
				}
			}
			else if((char1.getY()+charAccl) > 299)	
			{
				for (int i = 0; i < es.size(); i++ ) 
				{
					Enemies enemy = (Enemies) es.get(i);
					enemy.changeSpeed(2);
					this.scoreDif = 3;
					this.backDif = 4;
				}
			}
			else
			{
				for (int i = 0; i < es.size(); i++ ) 
				{
					Enemies enemy = (Enemies) es.get(i);
					enemy.changeSpeed(1);
				}
			}
				
			
			
			char1.movey(1);
		}
		
		return;
	}
	
	//Called whenever something on the board changes
	public void paint(Graphics g)
	{
		String score;
		super.paint(g);
		
		//Paints a 2D graphic of the background and its current position
		Graphics2D g2d = (Graphics2D) g;
		g2d.drawImage(Background1, 0, backY, null);
		
		//Paints the position of the character
		g2d.drawImage(char1.getImage(), char1.getX(), char1.getY(), this);
		
		//Creates an array list of bullets and paints each of them in turn
		ArrayList<Bullet> bs = char1.getBullets();
		  
		for (int i = 0; i < bs.size(); i++ ) 
		{
			Bullet b = (Bullet) bs.get(i);
			g2d.drawImage(b.getImage(), b.getX(), b.getY(), this);
		}
        
		//Each enemy is painted in turn
		for (int i = 0; i < es.size(); i++ ) 
		{
			Enemies enemy = (Enemies) es.get(i);
			g2d.drawImage(enemy.getImage(), enemy.getX(), enemy.getY(), this);
		}
		
		//Whenever the timer that creates enemies is stopped then an image
		//that tells the player that the game is paused
		if(!timerEnemy.isRunning())
		{
			g2d.drawImage(paused, 0, 0, null);
		}
		
		//Paints the score onto the screen. 
		//Changes color to white and creates and new more visible font for the score
		Color white = new Color(255,255,255);
		Font font1 = new Font("serif", Font.BOLD, 32);
		g.setFont(font1);
		g.setColor(white);
		g.drawString("SCORE: ", 100, 30);
		Font font2 = new Font("serif", Font.BOLD, 30);
		g.setFont(font2);
		score = Integer.toString((char1.getScore()));
		//Score will also move so that first number is always close to title
		g.drawString(score, 230 + ((score.length() - 4) * 3) , 30);
		
		//Should flush all changes to the display
		Toolkit.getDefaultToolkit().sync();
		g.dispose();

		
	}
	
	//Whenever the timer ticks over this will count as an action which will move
	//the position of the character, bullets and enemies
	public void actionPerformed(ActionEvent e)
	{	
		//Creates an array list of bullets that is gotten from the character object
		 ArrayList<Bullet> bs = char1.getBullets();
		 
		 	//Moves the bullets unless they are no longer visible.
		 	//Or unless the bullets are in close proximity
	        for (int i = 0; i < bs.size(); i++) 
	        {
	            Bullet bullet = (Bullet) bs.get(i);
	            
	            if((bs.size() > 1) && i>0)
	            {
	            	Bullet bullet2 = (Bullet) bs.get(i-1);
	            	
	            	if (bullet.isVisible()) 
	            	{
	            		bullet.move();
	                
	            		if((bullet2.getY() - bullet.getY()) < 60)
	            		{
	            			bs.remove(i);
	            		}
	            	}
	            	else 
	            		bs.remove(i);
	            }
	            else
	            {
	            	if (bullet.isVisible()) 
	            	{
	            		bullet.move();
	            	}
	            		else 
	            		{
		            		bs.remove(i);
	            		}
	            }
	        }
	        
	        //Calls each enemy and moves them in turn or removes them if they
	        //are no longer visible on the board
	        for (int i = 0; i < es.size(); i++) {
	            Enemies enemy = (Enemies) es.get(i);
	            if (enemy.isVisible()) 
	                enemy.move();
	                      
	            else
	            	es.remove(i);
	        }
	        
	    //Sets the position of the character and then repaints the whole board
		char1.movex();
		char1.movey(0);
		repaint();
		
	}
	
	
		
	
	public void startMusic()
	{
		
		AudioPlayer.player.start(as);
		
		
	}
	
	
	
	public void stopMusic()
	{		

		        AudioPlayer.player.stop(as);
						
	}
	
	
	
	
	
	
	//Adapter class that listens for a key being pressed, only works when the timerGame is running
	 private class TimeAdapter extends KeyAdapter {
		 
		 boolean muted = false;
		 
		 //Tells the character object that the key has been released
	        public void keyReleased(KeyEvent e) {
	            char1.keyRelease(e);
	        }

	        
	        //Sends the character the value of the key pressed unless P is pressed
	        //If P is pressed then all timers are stopped.
	        public void keyPressed(KeyEvent e) {
	        	

	        	int key = e.getKeyCode();
	        	if (key == KeyEvent.VK_P)
	            {
	            	if(!timerEnemy.isRunning()) {
	            		
	            		timerGame.start();
	            		timerEnemy.start();
	            		timerBack.start();
	            		timerChar.start();		
	            	}
	            	else
	            	{
	            		
	            		timerEnemy.stop();
	            		timerBack.stop();
	            		timerChar.stop();   
	            		timerGame.stop();
	            	}
	            	
	            }
	        	//If M is pressed then the sounds are either muted or unmuted
	        	else if((key == KeyEvent.VK_M))
	        	{
	        		if(!muted)
	        		{
	        			stopMusic();
	        			muted = true;
	        		}
	        		else
	        		{
	        			startMusic();
	        			muted = false;
	        		}
	        	}
	        	//If the esc key is pressed then the players will have the option to end the game
	        	else if((key == KeyEvent.VK_ESCAPE))
	        	{
		
	        		int endGame = JOptionPane.showConfirmDialog(null, "Are you sure you want to quit?", "Quit?",  JOptionPane.YES_NO_OPTION);	
	        		if(endGame == JOptionPane.YES_OPTION)
	        		{
	        			System.exit(0);
	        		}
	        		
	        	}
	        	else
	            char1.keyPress(e, muted);
	        }
	    } 	
}


